#include "gxcp.h"
#include "qe_log.h"
#include "qe_time.h"
#include "qe_bitops.h"
#include "qe_memory.h"
#include "qe_assert.h"



QELOG_DOMAIN("gxcp");


#define GXCP_HEX_DOMAIN     "gxcp-hexdump"

#define gxcp_hexdump(...)   qehex_debug(GXCP_HEX_DOMAIN, __VA_ARGS__)

#define GXCP_PREAMBLE       0x47584350  /* ASCII GXCP */

#define ACCESS_R            QE_BIT(0)
#define ACCESS_W            QE_BIT(1)

enum {
    GXCP_READMEM_CMD = 0,
    GXCP_READMEM_ACK,
    GXCP_WRITEMEM_CMD,
    GXCP_WRITEMEM_ACK,
    GXCP_EVENT_CMD = 0x8000,
    GXCP_EVENT_ACK,
};

/**
 * Bootstrap Register Map 
 */
enum {
    GXCP_BSR_PROTOCOL_VERSION           = 0x00000000,
    GXCP_BSR_MANUFACTURER_NAME          = 0x00000004,
    GXCP_BSR_MODEL_NAME                 = 0x00000044,
    GXCP_BSR_FAMILY_NAME                = 0x00000084,
    GXCP_BSR_DEVICE_VERSION             = 0x000000C4,
    GXCP_BSR_MANUFACTURER_INFO          = 0x00000104,
    GXCP_BSR_SERIAL_NUMBER              = 0x00000144,
    GXCP_BSR_USER_DEFINED_NAME          = 0x00000184,
    GXCP_BSR_DEVICE_CAPABILITY          = 0x000001C4,
    GXCP_BSR_MAX_RESPONSE_TIME          = 0x000001CC,
    GXCP_BSR_MANIFEST_TABLE_ADDRESS     = 0x000001D0,
    GXCP_BSR_SBRM_ADDRESS               = 0x000001D8,
    GXCP_BSR_DEVICE_CONFIGURATION       = 0x000001E0,
    GXCP_BSR_HEARTBEAT_TIMEOUT          = 0x000001E8,
    GXCP_BSR_MESSAGE_CHANNEL_ID         = 0x000001EC,
    GXCP_BSR_TIMESTAMP                  = 0x000001F0,
    GXCP_BSR_TIMESTAMP_LATCH            = 0x000001F8,
    GXCP_BSR_TIMESTAMP_INCREMENT        = 0x000001FC,
    GXCP_BSR_ACCESS_PRIVILEGE           = 0x00000204,
    GXCP_BSR_IMPL_ENDIANESS             = 0x0000020C,
};

/**
 * File Operation Registers
 */
enum {
    GXCP_FILE_LIST_ADDRESS              = 0x00001000,
    GXCP_FILE_SELECTOR                  = 0x00001004,
    GXCP_FILE_OPT_SELECTOR              = 0x00001084,
    GXCP_FILE_OPT_EXECUTE               = 0x00001088,
    GXCP_FILE_OPT_STATUS                = 0x0000108C,
    GXCP_FILE_OPT_RESULT                = 0x00001090,
    GXCP_FILE_OPEN_MODE                 = 0x00001094,
    GXCP_FILE_ACCESS_OFFSET             = 0x00001098,
    GXCP_FILE_ACCESS_LENGTH             = 0x0000109C,
    GXCP_FILE_SIZE                      = 0x000010A0,
    GXCP_FILE_ACCESS_BUFFER             = 0x00002000,
};

enum {
    GXCP_FILE_OPT_OPEN = 0,
    GXCP_FILE_OPT_CLOSE,
    GXCP_FILE_OPT_READ,
    GXCP_FILE_OPT_WRITE,
    GXCP_FILE_OPT_DELETE
};

enum {
    GXCP_FILE_OPEN_READ = 0,
    GXCP_FILE_OPEN_WRITE,
    GXCP_FILE_OPEN_RW,
};

typedef struct {
    qe_u32 addr;
    qe_const_str name;
    qe_u8 access;
} bootstrap_map;

static bootstrap_map bsr_map[] = {
    {GXCP_BSR_PROTOCOL_VERSION, "ProtocolVersion", ACCESS_R},
    {GXCP_BSR_MANUFACTURER_NAME, "ManufacturerName", ACCESS_R},
    {GXCP_BSR_MODEL_NAME, "ModelName", ACCESS_R},
    {GXCP_BSR_FAMILY_NAME, "FamilyName", ACCESS_R},
    {GXCP_BSR_DEVICE_VERSION, "DeviceVersion", ACCESS_R},
    {GXCP_BSR_MANUFACTURER_INFO, "ManufacturerInfo", ACCESS_R},
    {GXCP_BSR_SERIAL_NUMBER, "SerialNumber", ACCESS_R},
    {GXCP_BSR_USER_DEFINED_NAME, "UserDefinedName", ACCESS_R|ACCESS_W},
    {GXCP_BSR_DEVICE_CAPABILITY, "DeviceCapability", ACCESS_R},
    {GXCP_BSR_MAX_RESPONSE_TIME, "MaxResponseTime", ACCESS_R},
    {GXCP_BSR_MANIFEST_TABLE_ADDRESS, "ManifestTableAddress", ACCESS_R},
    {GXCP_BSR_SBRM_ADDRESS, "SBRMAddress", ACCESS_R},
    {GXCP_BSR_DEVICE_CONFIGURATION, "DeviceConfiguration", ACCESS_R|ACCESS_W},
    {GXCP_BSR_HEARTBEAT_TIMEOUT, "HeartbeatTimeout", ACCESS_R|ACCESS_W},
    {GXCP_BSR_MESSAGE_CHANNEL_ID, "MessageChannelID", ACCESS_R|ACCESS_W},
    {GXCP_BSR_TIMESTAMP, "Timestamp", ACCESS_R},
    {GXCP_BSR_TIMESTAMP_LATCH, "TimestampLatch", ACCESS_W},
    {GXCP_BSR_TIMESTAMP_INCREMENT, "TimestampIncrement", ACCESS_R},
    {GXCP_BSR_ACCESS_PRIVILEGE, "AccessPrivilege", ACCESS_R|ACCESS_W},
    {GXCP_BSR_IMPL_ENDIANESS, "ImplEndianess", ACCESS_R}
};

static qe_bool is_bsr(qe_u32 addr)
{
    if (addr <= GXCP_BSR_IMPL_ENDIANESS) {
        return qe_true;
    }

    return qe_false;
}

static qe_bool is_file_access(qe_u32 addr)
{
    if (addr <= GXCP_FILE_ACCESS_BUFFER + 1024 && addr >= GXCP_FILE_LIST_ADDRESS) {
        return qe_true;
    }
    return qe_false;
}

static bootstrap_map *find_map(qe_u64 addr)
{
    int i;
    int size = qe_array_size(bsr_map);
    bootstrap_map *map;

    for (i=0; i<size; i++) {
        map = &bsr_map[i];
        if (addr == map->addr) 
            return map;
    }

    return QE_NULL;
}

static qe_u8 fcs_calculate(qe_u8 *data, qe_size size)
{
    int i;
    qe_u8 sum = 0;

    for (i=0; i<size; i++) {
        sum ^= data[i];
    }

    return sum;
}

static gxcp_ret unpack_request(qe_u8 *data, qe_size size, gxcp_request *request)
{
    int length;
    qe_u8 *pos;
    qe_u8 fcs;
    gxcp_prefix *prefix = (gxcp_prefix *)data;
    gxcp_ccd_cmd *ccd = (gxcp_ccd_cmd *)prefix->data;

    //qe_debug("unpack prefix");
    //qe_debug("preamble   : %x", prefix->preamble);
    //qe_debug("channel id : %d", prefix->channel_id);
    //qe_debug("length     : %d", prefix->length);
    //qe_debug("fcs        : %x", prefix->fcs);

    /* Prefix check */
    if (prefix->preamble != GXCP_PREAMBLE) {
        qe_error("preamble error 0x%x", prefix->preamble);
        return GXCP_INVALID_HEADER;
    }

    if (prefix->length + GXCP_PREFIX_SIZE > size) {
        qe_error("length error %d %d", prefix->length, size);
        //gxcp_hexdump(data, size);
        return GXCP_INVALID_HEADER;
    }

    fcs = fcs_calculate(prefix->data, prefix->length);
    if (prefix->fcs != fcs) {
        qe_error("fcs error");
        //gxcp_hexdump(data, size);
        return GXCP_INVALID_HEADER;
    }

    /* Parse request */
    request->channel_id = prefix->channel_id;
    request->flags = ccd->flags;
    request->command_id = ccd->command;
    request->request_id = ccd->request_id;

    if (ccd->command == GXCP_WRITEMEM_CMD) {
        gxcp_scd_writemem *scd = (gxcp_scd_writemem *)ccd->data;
        request->writemem.address = scd->address;
        request->writemem.length = scd->length;
        request->writemem.data = qe_malloc(scd->length);
        qe_memcpy(request->writemem.data, scd->data, scd->length);
    } else if (ccd->command == GXCP_READMEM_CMD) {
        gxcp_scd_readmem *scd = (gxcp_scd_readmem *)ccd->data;
        request->readmem.address = scd->address;
        request->readmem.length = scd->length;
    } else {
        qe_error("unknown command %d", ccd->command);
        return GXCP_INVALID_PARAMETER;
    }
    
    return GXCP_SUCCESS;
}

static gxcp_ret bsr_read(gxcp_ctx *ctx, qe_u32 addr, qe_u8 *data, qe_size size)
{
    qe_u8 *ptr;
    bootstrap_map *map = find_map(addr);
    if (!map) {
        return GXCP_INVALID_ADDRESS;
    }

    if (!(map->access & ACCESS_R)) {
        return GXCP_ACCESS_DENIED;
    }
    ptr = (qe_u8 *)&ctx->bsr + addr;
    qe_info("read %s", map->name);
    qe_memcpy(data, ptr, size);
    return GXCP_SUCCESS;
}

static gxcp_ret file_operation_execute(gxcp_ctx *g)
{
    gxcp_ret ret = GXCP_SUCCESS;
    void *handle;
    gxcp_file_operation *fop = &g->file_op;

    //qe_debug("execute %d", fop->opt);

    switch (fop->opt) {

    case GXCP_FILE_OPT_OPEN:
        handle = g->cb.file_open(fop->filename, fop->open_mode);
        if (!handle) {
            qe_error("open %s failed", fop->filename);
            ret = qe_err_common;
            break;
        }
        fop->handle = handle;
        ret = GXCP_SUCCESS;
        break;

    case GXCP_FILE_OPT_CLOSE:
        g->cb.file_close(fop->handle);
        break;

    case GXCP_FILE_OPT_READ:
        ret = g->cb.file_read(fop->handle, qe_gbuf_pos(fop->buf), fop->length, &fop->result);
        break;

    case GXCP_FILE_OPT_WRITE:
        ret = g->cb.file_write(fop->handle, qe_gbuf_pos(fop->buf), fop->length, &fop->result);
        break;

    case GXCP_FILE_OPT_DELETE:
        ret = g->cb.file_delete(fop->filename);
        break;

    default:
        qe_error("unknown file operation %d", g->file_op.opt);
        return GXCP_INVALID_PARAMETER;
    }

    fop->status = ret;

    return ret;
}

static gxcp_ret file_operation_read(gxcp_ctx *g, qe_u32 addr, qe_u8 *data, qe_size size)
{
    qe_size filesize;
    gxcp_ret ret;
    gxcp_file_operation *fop = &g->file_op;

    switch (addr) {

    case GXCP_FILE_LIST_ADDRESS:
        qe_memcpy(data, &fop->filelist_address, 4);
        break;

    case GXCP_FILE_OPT_STATUS:
        qe_memcpy(data, &fop->status, 4);
        break;

    case GXCP_FILE_OPT_RESULT:
        qe_memcpy(data, &fop->result, 4);
        break;

    case GXCP_FILE_ACCESS_BUFFER:
        qe_debug("read buffer %d", size);
        qe_memcpy(data, qe_gbuf_pos(fop->buf), size);
        break;

    case GXCP_FILE_SIZE:
        ret = g->cb.file_size(fop->filename, &filesize);
        if (ret != GXCP_SUCCESS) {
            qe_error("get file size error:%d", ret);
            return ret;
        }
        qe_memcpy(data, &filesize, 4);
        break;
    }

    return GXCP_SUCCESS;
}

static gxcp_ret file_operation_write(gxcp_ctx *g, qe_u32 addr, qe_u8 *data, qe_size size)
{
    qe_u32 val = *(qe_u32 *)data;
    gxcp_file_operation *fop = &g->file_op;

    switch (addr) {

    case GXCP_FILE_SELECTOR:
        qe_memcpy(fop->filename, data, size);
        qe_debug("set filename %s", fop->filename);
        break;

    case GXCP_FILE_OPT_SELECTOR:
        qe_debug("set file opt mode %d", val);
        fop->opt = val;
        break;

    case GXCP_FILE_OPT_EXECUTE:
        file_operation_execute(g);
        break;

    case GXCP_FILE_OPEN_MODE:
        qe_debug("set file open mode %d", val);
        fop->open_mode = val;
        break;

    case GXCP_FILE_ACCESS_BUFFER:
        qe_debug("access buffer");
        qe_gbuf_memcpy(fop->buf, data, size);
        break;

    case GXCP_FILE_ACCESS_OFFSET:
        qe_debug("file asscess offset %d", val);
        g->file_op.offset = val;
        break;

    case GXCP_FILE_ACCESS_LENGTH:
        qe_debug("file asscess length %d", val);
        g->file_op.length = val;
        break;

    default:
        qe_error("unknown write for %x", addr);
        return GXCP_INVALID_ADDRESS;
    }

    return GXCP_SUCCESS;
}

static gxcp_ret request_process(gxcp_ctx *g, gxcp_request *request)
{
    qe_u32 addr;
    qe_u16 len;

    switch (request->command_id) {

    case GXCP_READMEM_CMD:
        addr = request->readmem.address;
        len = request->readmem.length;
        //qe_debug("readmem %x %d", addr, len);
        if (is_bsr(addr)) {
            return bsr_read(g, addr, qe_gbuf_pos(g->txbuf), len);
        } else if (is_file_access(addr)) {
            return file_operation_read(g, addr, qe_gbuf_pos(g->txbuf), len);
        } else {
            return g->cb.readmem(addr, qe_gbuf_pos(g->txbuf), len, g->user);
        }

    case GXCP_WRITEMEM_CMD:
        addr = request->readmem.address;
        len = request->readmem.length;
        //qe_debug("writemem %x %d", addr, len);
        if (is_file_access(addr)) {
            return file_operation_write(g, addr, request->writemem.data, len);
        }
        return g->cb.writemem(addr, request->writemem.data, len, g->user);

    default:
        return GXCP_NOT_IMPLEMENTED;
    }
}

static void pack_ack(qe_u16 channel_id, qe_pack_buffer *buf, gxcp_request *request, qe_u8 ret, qe_u8 *data)
{
    qe_u16 code;
    qe_u16 command_id;
    gxcp_prefix prefix;
    qe_pack_buffer payload;

    qe_pack_buffer_init(&payload);

    /* Pack CCD */
    /* CCD.StatusCode 2byte */
    code = ret;
    qe_pack_buffer_write(&payload, (const char *)&code, 2);
    /* CCD.CommandID 2byte */
    if (request->command_id == GXCP_WRITEMEM_CMD) {
        command_id = GXCP_WRITEMEM_ACK;
    } else {
        command_id = GXCP_READMEM_ACK;
    }
    qe_pack_buffer_write(&payload, (const char *)&command_id, 2);
    /* CCD.RequestID 2byte */
    qe_pack_buffer_write(&payload, (const char *)&request->request_id, 2);

    /* Pack SCD */
    if (request->command_id == GXCP_READMEM_CMD && ret == GXCP_SUCCESS) {
        /* SCD.Data */
        qe_pack_buffer_write(&payload, data, request->readmem.length);
    } else if (request->command_id == GXCP_WRITEMEM_CMD) {
        /* SCD.Length */
        qe_pack_buffer_write(&payload, (const char *)&request->writemem.length, 2);
    }

    prefix.preamble = GXCP_PREAMBLE;
    prefix.channel_id = channel_id;
    prefix.length = payload.size;
    prefix.fcs = fcs_calculate(payload.data, payload.size);
    qe_pack_buffer_write(buf, (const char *)&prefix, sizeof(prefix));
    qe_pack_buffer_write(buf, payload.data, payload.size);
    qe_pack_buffer_destroy(&payload);
}

static void ack_send(gxcp_ctx *g, gxcp_request *request, qe_int code)
{
    qe_ret ret;
    qe_pack_buffer ack;

    qe_pack_buffer_init(&ack);

    pack_ack(g->channel_id, &ack, request, code, qe_gbuf_pos(g->txbuf));

    //gxcp_hexdump(ack.data, ack.size);
    g->io.write(ack.data, ack.size, g->user);

    qe_pack_buffer_destroy(&ack);
}

static void request_destroy(gxcp_request *request)
{
    if (request->command_id == GXCP_WRITEMEM_CMD) {
        qe_free(request->writemem.data);
    }
}

qe_ret gxcp_rx_handle(gxcp_ctx *g, qe_u8 *data, qe_size size)
{
    qe_int ret;
    gxcp_request request;

    if (g->cb.writemem == QE_NULL ||
        g->cb.readmem == QE_NULL ||
        g->io.read == QE_NULL ||
        g->io.write == QE_NULL) {
        qe_error("callback or io is not set");
        return qe_err_param;
    }

    ret = unpack_request(data, size, &request);
    if (ret != GXCP_SUCCESS) {
        goto __exit;
    }

    //qe_debug("command id : %d", request.command_id);
    //qe_debug("request id : %d", request.request_id);

    ret = request_process(g, &request);

__exit:
    ack_send(g, &request, ret);
    request_destroy(&request);
    return qe_ok;
}

qe_ret gxcp_init(gxcp_ctx *g, qe_size bufsz, void *user)
{
    qe_memset(g, 0, sizeof(*g));
    g->rxbuf = qe_gbuf_new(bufsz);
    g->txbuf = qe_gbuf_new(bufsz);
    g->file_op.buf = qe_gbuf_new(bufsz);
    qe_assert(g->txbuf);
    qe_assert(g->rxbuf);
    g->user = user;
    g->request_id = 0;
    g->channel_id = 0;
    return qe_ok;
}

void gxcp_set_io(gxcp_ctx *g, gxcp_io *io)
{
    g->io.read = io->read;
    g->io.write = io->write;
}

void gxcp_set_callback(gxcp_ctx *g, gxcp_callback *cb)
{
    g->cb.event = cb->event;
    g->cb.readmem = cb->readmem;
    g->cb.writemem = cb->writemem;
    g->cb.file_open = cb->file_open;
    g->cb.file_close = cb->file_close;
    g->cb.file_read = cb->file_read;
    g->cb.file_write = cb->file_write;
    g->cb.file_size = cb->file_size;
    g->cb.file_delete = cb->file_delete;
}

void gxcp_set_bootstrap(gxcp_ctx *g, gxcp_bootstrap_regs *bsr)
{
    qe_memcpy(&g->bsr, bsr, sizeof(*bsr));
}

void gxcp_set_channel_id(gxcp_ctx *g, qe_u16 channel_id)
{
    g->channel_id = channel_id;
}

void gxcp_set_filelist(gxcp_ctx *g, qe_u32 address)
{
    g->file_op.filelist_address = address;
}